// ATENÇÃO >>>>>>> AINDA NÃO TESTADO o LOG dos relss
// TODO, colocar escolha de idiomas
// TODO uma vez conectado confrontar com as configurações
// TODO, ler todos os parametros do aparelho após reconfig
// No menu status clicar no botão de rele para ligar ou desligar ele quando o worktemp estiver
//      configurado como escravo
// TODO aplicar mudanças com as leituras do arquivo de configuração
    // TODO ler, gravar operadores dos reles
// TODO todos os menus btnMenuXXXX.Tag := 0 quando desconectar;
// TODO. Colocar novos limites de valore nos edits após atualizar os edits de dependencias

unit main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
  FMX.StdCtrls, FMX.ListBox, FMX.Objects, FMX.Controls3D, System.Math.Vectors,
  FMX.Objects3D, System.Rtti, FMX.Grid, FMX.SpinBox, IWSystem,
  FMX.Controls.Presentation, FMX.Edit, IniFiles, FMX.TabControl, FMX.Colors,
  FMX.Memo, FMX.DateTimeCtrls, FMX.ExtCtrls, FMX.EditBox, FMX.NumberBox,
  FMXTee.Engine, FMXTee.Procs, FMXTee.Chart,

  // minhas libs
  u_terminal, u_modbus, FMX.ScrollBox;

const
    clERROR = TAlphaColorRec.Red;
    clOK = TAlphaColorRec.Black;
    styleWINDOWS = 0;
    styleTRANSP = 1;
    styleJET = 2;
    MODEL_UNKNOW = 'Terminal de acesso aos termômetros';

type
  TfrmMain = class(TForm)
    timerProcess: TTimer;
    StatusBar1: TStatusBar;
    StyleBookJet: TStyleBook;
    StyleBookTransp: TStyleBook;
    lblStatus: TLabel;
    Label3: TLabel;
    Grid3D1: TGrid3D;
    StyleBookWindows: TStyleBook;
    pnlModel: TPanel;
    lblModel: TLabel;
    Panel10: TPanel;
    btnMenuMain: TButton;
    btnMenuStatus: TButton;
    TabControl: TTabControl;
    TabItem5: TTabItem;
    pnlConnection: TPanel;
    Label6: TLabel;
    Label10: TLabel;
    edtID: TNumberBox;
    spiTimeout: TSpinBox;
    btnCnt: TButton;
    cbxPort: TComboBox;
    Label12: TLabel;
    btnUpDatePorts: TButton;
    LayoutIndConnect: TLayout;
    indCnt: TAniIndicator;
    lblIndCon: TLabel;
    pnlMain: TPanel;
    TabItem11: TTabItem;
    pnlStatus: TPanel;
    Panel9: TPanel;
    Label33: TLabel;
    Panel15: TPanel;
    Label34: TLabel;
    btnMenuAbout: TButton;
    TabItem2: TTabItem;
    pnlAbout: TPanel;
    Label70: TLabel;
    Label21: TLabel;
    Label17: TLabel;
    cbxStyle: TComboBox;
    btnStatusReadC: TButton;
    cbxUpdate: TCheckBox;
    btnMenuSair: TButton;
    GroupBox4: TGroupBox;
    memoLog: TMemo;
    btnClear: TButton;
    Label1: TLabel;
    pnlS1: TPanel;
    lblMinTS1: TLabel;
    lblTS1: TLabel;
    lblMaxTS1: TLabel;
    Label32: TLabel;
    Panel1: TPanel;
    Label4: TLabel;
    btnSetTempJ: TButton;
    btnSetTempC: TButton;
    btnSetTempF: TButton;
    btnInitStatC: TButton;
    pnlS2: TPanel;
    lblMinTS2: TLabel;
    lblTS2: TLabel;
    lblMaxTS2: TLabel;
    Label8: TLabel;
    pnlS3: TPanel;
    lblMinTS3: TLabel;
    lblTS3: TLabel;
    lblMaxTS3: TLabel;
    Label14: TLabel;
    pnlS4: TPanel;
    lblMinTS4: TLabel;
    lblTS4: TLabel;
    lblMaxTS4: TLabel;
    Label19: TLabel;
    pnlS5: TPanel;
    lblMinTS5: TLabel;
    lblTS5: TLabel;
    lblMaxTS5: TLabel;
    Label24: TLabel;
    pnlS6: TPanel;
    lblMinTS6: TLabel;
    lblTS6: TLabel;
    lblMaxTS6: TLabel;
    Label28: TLabel;
    pnlS7: TPanel;
    lblMinTS7: TLabel;
    lblTS7: TLabel;
    lblMaxTS7: TLabel;
    Label35: TLabel;
    pnlS8: TPanel;
    lblMinTS8: TLabel;
    lblTS8: TLabel;
    lblMaxTS8: TLabel;
    Label39: TLabel;
    pnlS9: TPanel;
    lblMinTS9: TLabel;
    lblTS9: TLabel;
    lblMaxTS9: TLabel;
    Label43: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnCntClick(Sender: TObject);
    procedure timerProcessTimer(Sender: TObject);
    procedure btnMenuSairClick(Sender: TObject);
    procedure btnInitStatClick(Sender: TObject);
    procedure cbxStyleChange(Sender: TObject);
    procedure spiTimeoutChange(Sender: TObject);
    procedure btnUpDatePortsClick(Sender: TObject);
    procedure btnClearClick(Sender: TObject);
    procedure btnStatusReadClick(Sender: TObject);
    procedure btnMenuMainClick(Sender: TObject);
    procedure btnMenuStatusClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnMenuAboutClick(Sender: TObject);
    procedure btnSetTempJClick(Sender: TObject);
    procedure btnSetTempCClick(Sender: TObject);
    procedure btnSetTempFClick(Sender: TObject);
  private
    { Private declarations }
    StyleForm:integer;
    Flog:textfile;
    SendCmdTR: boolean;
    function Report(const color: TAlphaColor; const Format: string; const Args: array of const): string; overload;
    function Report(const color: TAlphaColor; const Format: string): string; overload;
    procedure InitPrint;
    procedure CompsState;
    function SendCommand(cmd: tCommand):integer;
    procedure ModbusError(codError:integer);
    procedure InitPrintTemp(lbl, lblMin, lblMax:tlabel);
    procedure SetTemp;
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;
  fileInit:TIniFile;
  Terminal:tTerminal;
  SaveLog:boolean;
  RelaySet:integer;
  idxOpMat: integer;

implementation

{$R *.fmx}

// #################################################################################################
// SYSTEM
// #################################################################################################
procedure TfrmMain.FormCreate(Sender: TObject);
begin
    Terminal:= tTerminal.Create;
    fileInit := TIniFile.Create(gsAppPath+gsAppName+'.ini');

    SaveLog := fileInit.ReadBool('SYSTEM','SAVE_LOG', FALSE);
    Terminal.SaveLog := fileInit.ReadBool('MODBUS','SAVE_LOG', FALSE);

    TabControl.Visible := FALSE;
    btnMenuMainClick(NIL);

    btnUpDatePortsClick(NIL);
    cbxPort.ItemIndex := fileInit.ReadInteger('MODBUS','PORT', 0);
    Terminal.BaudRate:= 57600;
    Terminal.Timeout:= fileInit.ReadInteger('MODBUS','TIMEOUT', 3);

    edtID.Value := fileInit.ReadInteger('MODBUS','ADDRESS', 201);
    spiTimeout.Value:= Terminal.Timeout;

    lblModel.Text := MODEL_UNKNOW;
    InitPrint;
    LayoutIndConnect.Visible := FALSE;
    indCnt.Enabled := TRUE;
    sendCmdTR := TRUE;

    if (SaveLog) then begin
        assignfile(Flog, gsAppName+'.log');
        Rewrite(Flog);
        writeLn(Flog, ' >>>>>>>>> INICIAÇÃO DO SISTEMA - Data: ' + DateToStr(date) + ' Hora:' + TimeToStr(time) + ' <<<<<<<< ');
    end;

    CompsState;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
    cbxStyle.ItemIndex := fileInit.ReadInteger('SYSTEM','STYLE', styleTRANSP);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
    fileInit.WriteBool('SYSTEM','SAVE_LOG', SaveLog);
    fileInit.WriteBool('MODBUS','SAVE_LOG', Terminal.SaveLog);

    fileInit.WriteInteger('SYSTEM','STYLE', StyleForm);
    fileInit.WriteInteger('MODBUS','PORT', cbxPort.ItemIndex);
    fileInit.WriteInteger('MODBUS','ADDRESS', round(edtID.Value));
    fileInit.WriteInteger('MODBUS','TIMEOUT', Terminal.Timeout);

    fileInit.Free;
    Terminal.Free;

    if (SaveLog) then CloseFile(FLog);
end;

procedure TfrmMain.btnMenuSairClick(Sender: TObject);
begin
    Application.Terminate;
end;

// #################################################################################################
// REPORT
// #################################################################################################
function TfrmMain.Report(const color: TAlphaColor; const Format: string; const Args: array of const): string;
begin
    fmtStr(result, format, args);
    lblStatus.TextSettings.FontColor := color;
    lblStatus.Text := Result;
    memoLog.lines.add(Result);
    memoLog.GoToTextEnd;
end;

function TfrmMain.Report(const color: TAlphaColor; const Format: string): string;
begin
    Report(color, Format,[]);
end;

procedure TfrmMain.btnClearClick(Sender: TObject);
begin
    memoLog.Lines.Clear;
end;

// #################################################################################################
// AUX
// #################################################################################################
procedure ApllyStyle(Style:integer);
begin
    case Style of
    styleWINDOWS: begin
        frmMain.StyleBook := frmMain.StyleBookWindows;
    end;
    styleTRANSP: begin
        frmMain.StyleBook := frmMain.StyleBookTransp;
    end;
    styleJET: begin
        frmMain.StyleBook := frmMain.StyleBookJet;
    end;
    end;

    frmMain.StyleForm := Style;
end;

function convCelcius2Fahrenheit(t:real):real;
begin
    result:= t*1.8+32;
end;

function convFahrenheit2Celcius(t:real):real;
begin
    result:= (t-32)/1.8;
end;

procedure TfrmMain.cbxStyleChange(Sender: TObject);
begin
    ApllyStyle(cbxStyle.ItemIndex);
end;

procedure TfrmMain.CompsState;
begin
    btnMenuStatus.Enabled := Terminal.Connected;

    cbxPort.Enabled := not Terminal.Connected;
    edtID.Enabled := not Terminal.Connected;
    spiTimeout.Enabled := not Terminal.Connected;
    btnUpDatePorts.Enabled := not Terminal.Connected;

    timerProcess.Enabled := Terminal.Connected;

    if (not Terminal.Connected) then begin
        btnMenuMain.Tag := 0;
        btnMenuStatus.Tag := 0;
        btnMenuAbout.Tag := 0;
        btnMenuSair.Tag := 0;
    end;

    //    for i:=0 to pnlCtrl.ControlCount -1 do begin
    //        if  (pnlCtrl.Controls[i] is TButton) or
    //            (pnlCtrl.Controls[i] is TCheckBox)
    //        then
    //            pnlCtrl.Controls[i].Enabled := portOpen;
    //    end;
end;

procedure TfrmMain.ModbusError(codError:integer);
var msg:string;
begin
    case codError of
    errMODBUS_ILLEGAL_FUNCTION: begin
        msg := 'ILLEGAL FUNCTION';
    end;
    errMODBUS_ILLEGAL_DATA_ADDRESS: begin
        msg := 'ILLEGAL DATA ADDRESS';
    end;
    errMODBUS_ILLEGAL_DATA_VALUE: begin
        msg := 'ILLEGAL DATA VALUE';
    end;
    errMODBUS_SLAVE_DEVICE_FAILURE: begin
        msg := 'SLAVE DEVICE FAILURE';
    end;
    errMODBUS_TIMEOUT: begin
        msg := 'TIMEOUT';
    end else begin
        msg := format('Unknow %d',[codError]);
    end;
    end;

    report(0, 'Modbus error %s',[msg]);
    MessageDlg(
            msg,
            TMsgDlgType.mtWarning,
            [TMsgDlgBtn.mbOK],
            0
    );

    if (SaveLog) then writeLn(Flog, format('Modbus error %s',[msg]));
end;


// #################################################################################################
// CONEXÃO
// #################################################################################################
function TfrmMain.SendCommand(cmd: tCommand):integer;
var ret: integer;
begin
    repeat
        ret := Terminal.SendCommand(cmd);
    until (ret <> errMODBUS_BUSY);

    result := ret;
end;

procedure TfrmMain.spiTimeoutChange(Sender: TObject);
begin
    Terminal.Timeout := round(spiTimeout.Value);
end;

procedure TfrmMain.btnCntClick(Sender: TObject);
var id:integer;
    port:string;
begin
    lblModel.Text := MODEL_UNKNOW;

    if (Terminal.Connected) then begin
        Terminal.CloseConnection;
        LayoutIndConnect.Visible := FALSE;  // Esconde o painel visor de conexão

        if (SaveLog) then writeLn(Flog, 'FECHANDO CONEXÃO');
        btnCnt.Text := 'Conectar';
        Report(clOK, 'Desconectado do dispositivo');
        InitPrint;
        CompsState;
        exit;
    end;

    if (cbxPort.ItemIndex < 0) then begin
        if (SaveLog) then writeLn(Flog, '  Não há porta serial selecionada.');
        Report(clOK, 'Não há porta serial selecionada.');
        MessageDlg(
            'Não há porta serial selecionada.'+#13+
            'Cheque se há porta serial disponível no seu PC, ou se o adaptador USB<>seri0l está conectado à porta USB.',
            TMsgDlgType.mtWarning,
            [TMsgDlgBtn.mbOk],
            0
        );
        exit;
    end;

    btnCnt.Enabled := FALSE;
    port:= cbxPort.Items[cbxPort.ItemIndex];
    id := round(edtID.Value);
    LayoutIndConnect.Visible := TRUE;
    if (SaveLog) then writeLn(Flog, 'ABRINDO CONEXÃO');
    if (SaveLog) then writeLn(Flog, format('  Conectando ao WorkTemp id %d porta %s',[id, port]));
    Report(clOK, 'Conectando ao WorkTemp id %d porta %s',[id, port]);

    if (Terminal.OpenConnection(port, id) < 0) then begin
        MessageDlg(
            'Não foi possível fazer conexão com o WorkTemp, por favor cheque os seguintes itens:'+#13+
            '1 - Se o WorkTemp está ligado' +#13+
            '2 - Se há porta de comunicação COM' +#13+
            '3 - Se o cabo está conectado entre o computador ao WorkTemp'+#13+
            '4 - Se a porta de comunicação esteja sendo usado por outro software',
            TMsgDlgType.mtWarning,
            [TMsgDlgBtn.mbOk],
            0
        );

        btnCnt.Text := 'Conectar';
        Report(clERROR, 'Erro [%d] de conexão ao WorkTemp',[Terminal.ErrorCom]);
        if (SaveLog) then writeLn(Flog, '  Não estabelecido a comunicação');
        if (SaveLog) then writeLn(Flog, format('  Erro [%d] de conexão ao WorkTemp',[Terminal.ErrorCom]));
    end else begin
        Report(clERROR, 'Conectado ao dispositivo %si firmware versão %s',[Terminal.Model, Terminal.Fimware]);
        if (SaveLog) then writeLn(frmMain.Flog, format('Conectado ao dispositivo %s firmware versão %s',[Terminal.Model, Terminal.Fimware]));
        lblModel.Text := format('WorkTemp %s - Firmware %s',[Terminal.Model, Terminal.Fimware]);

        btnCnt.Text := 'Desconectar';
    end;

    CompsState;
    LayoutIndConnect.Visible := FALSE;
    btnCnt.Enabled := TRUE;
end;

procedure TfrmMain.btnUpDatePortsClick(Sender: TObject);
var qtdPort:integer;
    i:integer;
begin
    qtdPort := Terminal.GetPorts(cbxPort.Items);
    if ( qtdPort <= 0) then begin
        cbxPort.ItemIndex := -1;
        report(clERROR, 'Nenhuma porta COM foi encontrada',[qtdPort]);
    end else begin
        cbxPort.ItemIndex := 0;
        report(clOK, 'Portas COMs encontradas:');
        for i:= 0 to cbxPort.Items.Count -1 do
             report(clOK, '    %s',[(cbxPort.Items[i])]);
        report(clOK, 'Encontrado %d portas COMs',[qtdPort]);
    end;
end;

// #################################################################################################
// MENU
// #################################################################################################
procedure TfrmMain.btnMenuMainClick(Sender: TObject);
begin
    pnlConnection.Parent := pnlMain;
    pnlStatus.Parent := NIL;
    pnlAbout.Parent := NIL;
    cbxUpdate.Enabled := FALSE;
end;

procedure TfrmMain.btnMenuStatusClick(Sender: TObject);
begin
    pnlConnection.Parent := NIL;
    pnlStatus.Parent := pnlMain;
    btnStatusReadClick(NIL);
    pnlAbout.Parent := NIL;
    cbxUpdate.Enabled := FALSE;
end;

procedure TfrmMain.btnMenuAboutClick(Sender: TObject);
begin
    pnlConnection.Parent := NIL;
    pnlStatus.Parent := NIL;
    pnlAbout.Parent := pnlMain;
    cbxUpdate.Enabled := FALSE;
end;

// #################################################################################################
// TEMPERATURAS
// #################################################################################################
procedure TfrmMain.InitPrintTemp(lbl, lblMin, lblMax:tlabel);
begin
    lbl.Tag := -1000000;
    lbl.Text := '---';
    lblMin.Text := '---  ';
    lblMax.Text := '---  ';
end;

procedure PrintTemp (lbl, lblMin, lblMax:tlabel; temp:tSensor);
var s:string;
begin
    if ( (lbl.tag <> temp.value) or (lbl.tag <> temp.sts) ) then begin
        if (temp.sts = stsTEMP_READING) then begin
            lbl.tag := stsTEMP_READING;
            lbl.Text:= 'Lendo';
            lblMin.Text:= '---  ';
            lblMax.Text:= '---  ';
        end else if (temp.sts = stsTEMP_ERRMIN) then begin
            lbl.tag := stsTEMP_ERRMIN;
            lbl.Text:= 'Curto';
            lblMin.Text:= '---  ';
            lblMax.Text:= '---  ';
        end else if (temp.sts = stsTEMP_ERRMAX) then begin
            lbl.tag := stsTEMP_ERRMAX;
            lbl.Text:= 'Aberto';
            lblMin.Text:= '---  ';
            lblMax.Text:= '---  ';
        end else if (temp.sts = stsTEMP_OK) then begin
            if (temp.TemperatureType = 0) then  s:= 'ºC' else s:= 'ºF';
            lbl.tag := temp.value;
            lbl.Text:= floatToStr(temp.value/10) + s;
            lblMin.Text:= 'Min: ' + floatToStr(temp.valueMin/10) + s + '  ';
            lblMax.Text:= 'Max: ' + floatToStr(temp.valueMax/10) + s + '  ';
        end else begin
            lbl.tag := temp.sts;
            lbl.Text:= '---';
            lblMin.Text:= '---  ';
            lblMax.Text:= '---  ';
        end;
    end;
end;


procedure TfrmMain.InitPrint;
begin
    InitPrintTemp(lblTS1, lblMinTS1, lblMaxTS1);
    InitPrintTemp(lblTS2, lblMinTS2, lblMaxTS2);
    InitPrintTemp(lblTS3, lblMinTS3, lblMaxTS3);
    InitPrintTemp(lblTS4, lblMinTS4, lblMaxTS4);
    InitPrintTemp(lblTS5, lblMinTS5, lblMaxTS5);
    InitPrintTemp(lblTS6, lblMinTS6, lblMaxTS6);
    InitPrintTemp(lblTS7, lblMinTS7, lblMaxTS7);
    InitPrintTemp(lblTS8, lblMinTS8, lblMaxTS8);
    InitPrintTemp(lblTS9, lblMinTS9, lblMaxTS9);
end;

procedure TfrmMain.timerProcessTimer(Sender: TObject);
begin
    if (not cbxUpdate.IsChecked) then exit;

    // vamos desligar o timer para não haver overload das chamadas das funções abaixo
    // pois algumas funções são bem grandes
    timerProcess.Enabled:= FALSE;
    timerProcess.Tag := 1;
    btnStatusReadClick(NIL);
    timerProcess.Enabled:= TRUE;
    timerProcess.Tag := 0;
end;

procedure TfrmMain.btnInitStatClick(Sender: TObject);
var ret:integer;
begin
    ret := SendCommand(cmdGET_ACTION);
    if (ret < 0) then begin
        ModbusError(ret);
        exit;
    end;

    if (terminal.Action > 0) then begin
        MessageDlg(
            'Não foi possível executar esse comando porque há uma ação sendo executada no recurso de hardware.' + #13 +
            'Esta ação pode ser um experimento ou configurações dos termômetros em andamento',
            TMsgDlgType.mtWarning,
            [TMsgDlgBtn.mbOk],
            0
        );
        exit;
    end;

    report(0, 'Envio de comando de reinicio da estatísticas');
    ret := SendCommand(cmdRST_STAT_TEMP);

    if (ret < 0) then begin
        ModbusError(ret);
        exit;
    end;

    btnStatusReadClick(NIL);
    report(0, 'Reinicio da estatística feito com sucesso');
end;

procedure TfrmMain.btnStatusReadClick(Sender: TObject);
var ret :integer;
begin
    if (timerProcess.Tag = 0) then report(0, 'Envio de comando para ler temperaturas');
    ret := SendCommand(cmdGET_TEMP);

    if ret > 0 then begin
        printTemp(lblTS1, lblMinTS1, lblMaxTS1, Terminal.Temperature[0]);
//        printTemp(lblTS2, lblMinTS2, lblMaxTS2, Terminal.Temperature[1]);
//        printTemp(lblTS3, lblMinTS3, lblMaxTS3, Terminal.Temperature[2]);
//        printTemp(lblTS4, lblMinTS4, lblMaxTS4, Terminal.Temperature[3]);
//        printTemp(lblTS5, lblMinTS5, lblMaxTS5, Terminal.Temperature[4]);
//        printTemp(lblTS6, lblMinTS6, lblMaxTS6, Terminal.Temperature[5]);
//        printTemp(lblTS7, lblMinTS7, lblMaxTS7, Terminal.Temperature[6]);
//        printTemp(lblTS8, lblMinTS8, lblMaxTS8, Terminal.Temperature[7]);
//        printTemp(lblTS9, lblMinTS9, lblMaxTS9, Terminal.Temperature[8]);
    end;

    if (ret < 0) then begin
        ModbusError(ret);
        exit;
    end;

    if (timerProcess.Tag = 0) then report(0, 'Leituras feitas com sucesso');
end;

procedure TfrmMain.SetTemp;
var ret :integer;
begin
    ret := SendCommand(cmdGET_ACTION);
    if (ret < 0) then begin
        ModbusError(ret);
        exit;
    end;

    if (terminal.Action > 0) then begin
        MessageDlg(
            'Não foi possível executar esse comando porque há uma ação sendo executada no recurso de hardware.' + #13 +
            'Esta ação pode ser um experimento ou configurações dos termômetros em andamento',
            TMsgDlgType.mtWarning,
            [TMsgDlgBtn.mbOk],
            0
        );
        exit;
    end;

    // TODO. checar se o RH está fazendo alguma ação
    report(0, 'Ajustando grandeza da temperatura');

    ret := SendCommand(cmdSET_TEMP_TYPE);
    if (ret < 0) then begin
        ModbusError(ret);
        exit;
    end;

    report(0, 'Leituras feitas com sucesso');
end;

procedure TfrmMain.btnSetTempJClick(Sender: TObject);
begin
    Terminal.SetTempType := 0;
    SetTemp;
end;

procedure TfrmMain.btnSetTempCClick(Sender: TObject);
begin
    Terminal.SetTempType := 1;
    SetTemp;
end;

procedure TfrmMain.btnSetTempFClick(Sender: TObject);
begin
    Terminal.SetTempType := 2;
    SetTemp;
end;

end.
